package com.xiyuan.smartutils.logging;

import com.xiyuan.smartutils.logging.appender.ConsoleAppender;
import com.xiyuan.smartutils.logging.appender.FileAppender;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

final class LoggerImpl implements Logger {
    private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
    private static final Pattern FS_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
    private String name;
    private String clzName;
    private LogConfig config;
    private FileAppender file;
    
    
    protected LoggerImpl(String name) {
        this.clzName = this.getClass().getName();
        this.name = name;
        this.file = new FileAppender();
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public void trace(Object message) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        
        log(Level.ALL, ls);
    }
    
    @Override
    public void trace(Object message, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.ALL, ls);
    }
    
    @Override
    public void trace(Object message, Throwable ex, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        ls.add(ex);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.ALL, ls);
    }
    
    @Override
    public void debug(Object message) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        
        log(Level.DEBUG, ls);
    }
    
    @Override
    public void debug(Object message, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.DEBUG, ls);
    }
    
    @Override
    public void debug(Object message, Throwable ex, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        ls.add(ex);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        log(Level.DEBUG, ls);
    }
    
    @Override
    public void info(Object message) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        log(Level.INFO, ls);
    }
    
    @Override
    public void info(Object message, Object... arguments) {
        
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.INFO, ls);
    }
    
    @Override
    public void info(Object message, Throwable ex, Object... arguments) {
        
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        ls.add(ex);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.INFO, ls);
    }
    
    @Override
    public void warn(Object message) {
        
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        
        log(Level.WARN, ls);
    }
    
    @Override
    public void warn(Object message, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.WARN, ls);
    }
    
    @Override
    public void warn(Object message, Throwable ex, Object... arguments) {
        
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        ls.add(ex);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.WARN, ls);
    }
    
    @Override
    public void error(Object message) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        
        log(Level.ERROR, ls);
    }
    
    @Override
    public void error(Object message, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.ERROR, ls);
    }
    
    @Override
    public void error(Object message, Throwable ex, Object... arguments) {
        List<Object> ls = new ArrayList<>();
        ls.add(message);
        ls.add(ex);
        if (arguments != null) {
            ls.addAll(Arrays.asList(arguments));
        }
        
        log(Level.ERROR, ls);
    }
    
    @Override
    public Logger setLevel(Level level) {
        file.getConfig().setMinLevel(level);
        return this;
    }
    
    public LogConfig getConfig() {
        return config;
    }
    
    @Override
    public Logger setConfig(LogConfig config) {
        LoggerManager.addConfig(name, config);
        return this;
    }
    
    private void configure() {
        LogConfig config = LoggerManager.getConfig(this.name);
        if (config != this.config) {
            this.config = config;
            this.file.setConfig(config);
        }
    }
    
    private void log(Level level, List<Object> params) {
        
        // 更新配置
        this.configure();
        
        // 构建 日志记录
        LogRecord record = build(level, params);
        
        // 输出到文件
        file.append(record);
        
        // 输出到控制台
        if (config.isConsole()) {
            ConsoleAppender console = LoggerManager.getConsole();
            console.append(record);
        }
    }
    
    protected final LogRecord build(Level level, List<Object> params) {
        if (params == null) {params = new ArrayList<>();}
        
        if (params.isEmpty()) {params.add("");}
        
        Throwable ex = null;
        
        // 剔除第一个 message
        Object msg = params.remove(0);
        if (msg instanceof Throwable) {
            ex = (Throwable) msg;
        }
        
        // 再尝试 判断第一个是否为 异常
        if (ex == null && !params.isEmpty() && (params.get(0)) instanceof Throwable) {
            ex = (Throwable) params.remove(0);
        }
        
        // 尝试获取最后一个是否为 异常
        if (ex == null && !params.isEmpty() && (params.get(params.size() - 1)) instanceof Throwable) {
            ex = (Throwable) params.remove(params.size() - 1);
        }
        
        // 处理 String.format
        StringBuilder strb = new StringBuilder();
        String patten = String.valueOf(msg);
        Matcher m = FS_PATTERN.matcher(patten);
        
        int end = 0;
        for (int i = 0, start = 0; !params.isEmpty() && m.find(); i++) {
            end = m.start();
            strb.append(patten, start, end);
            strb.append(String.format(m.group(), params.get(i)));
            start = m.end();
            end = m.end();
            if (params.size() <= i + 1) {
                break;
            }
        }

        if (end < patten.length()) {strb.append(patten.substring(end));}

        for (int i = 0; i < strb.length(); i++) {
            char c = strb.charAt(i);
            if (c == '\'') {strb.insert(i++, '\'');}
        }
        
        // 处理消息 MessageFormat.format
        // 创建日志记录
        LogRecord record = new LogRecord();
        record.setName(name);
        record.setLevel(level);
        record.setMessage(strb.toString());
        record.setParams(params);
        record.setThrown(ex);
        StackTraceElement target = getStackTrace();
        record.setClazz(target.getClassName());
        record.setMethod(target.getMethodName());
        record.setFileName(target.getFileName());
        record.setLineNumber(target.getLineNumber());
        return record;
    }
    
    private StackTraceElement getStackTrace() {
        StackTraceElement target = null;
        StackTraceElement[] ls = new Throwable().getStackTrace();
        for (int i = 0; i < ls.length; i++) {
            StackTraceElement elm = ls[i];
            if (!clzName.equals(elm.getClassName())) {
                target = elm;
                break;
            }
        }
        if (target == null && ls.length > 0) {
            target = ls[ls.length - 1];
        }
        return target;
    }
}
